<?php
/*
 * 搜索基础
 */
abstract class Search_Base
{
	// 只读数据库配置名
	const _CONST_DB_READONLY_GROUP_ = 'readonly';

	protected $_query      = null;		// DB SELECT对象
	protected $_key_cache  = null;		// 缓存的搜索KEY
	protected $_use_cache  = FALSE;
	protected $_tb_main    = '';
	protected $_tb_main_as = '';
	protected $_tb_main_id = '';
	protected $_orig_query = NULL;
	protected $_enable_distinct = FALSE;
	protected $_db_group   = 'default';

	public function __construct ($tb_main, $tb_main_as, $tb_main_id = 'id')
	{
		$this->_tb_main    = $tb_main;
		$this->_tb_main_as = $tb_main_as;
		$this->_tb_main_id = $tb_main_id;
	}

	/*
	 * 搜索
	 */
	public function search ($query, $start = NULL, $limit = NULL)
	{
		$this->prepare_search($query);
		return $this->search_result ($query, $start, $limit);
	}

	/**
	 * 设置搜索使用的Database 配置
	 *
	 * @param	string		db group name
	 * @return	void
	 */
	protected function set_db_group ($db_group)
	{
		$groups = array_keys((array) Kohana::config('database'));
		if (in_array($db_group, $groups))
			$this->_db_group = $db_group;
	}

	/*
	 * 搜索前的参数过滤, 初始化工作
	 */
	protected function prepare_search (& $query)
	{
		$this->_query = NULL;
		$this->_orig_query = $query;
		$this->_enable_distinct = FALSE;

		if (isset($query['auto_cached']) && $query['auto_cached'])
		{
			$this->_use_cache = TRUE;
		}

		//
		// 设置数据库源
		//
		$enable_readonly = Arr::get($query, 'enable_readonly', FALSE);
		if ($enable_readonly)
			$this->set_db_group (self::_CONST_DB_READONLY_GROUP_);

		unset($query['auto_cached']);
	}

	protected function search_result ($query, $start, $limit)
	{
		$search_key     = NULL;
		if ($this->_use_cache)
		{
			$search_key = $this->search_key ($query, $start, $limit);
			$result     = $this->cache_result ($search_key);
			if (FALSE !== $result)
				return $result;
		}

		// count
		$count = 0; 
		$enable_search_count = Arr::get($query, 'enable_search_count', TRUE);
		if ($enable_search_count)
		{
			$this->_query = DB::select(
				$this->_enable_distinct ? 
					array('count(distinct("'.$this->_tb_main_as.'.'.$this->_tb_main_id.'"))', 'count')  :
					array('count("'.$this->_tb_main_as.'.'.$this->_tb_main_id.'")', 'count')
				)
				->from(array($this->_tb_main, $this->_tb_main_as));
			$this->build_where ($query, 'count');
			$count = $this->_query->execute($this->_db_group)->get('count');
			$this->_query = NULL;
		}


		// list 
		/////////////////////////////////////////////////////////////////////////
		$list = array();
		$enable_search_list = Arr::get($query, 'enable_search_list', TRUE);
		if ($enable_search_list)
		{
			// build selecting
			$this->_query = DB::select();
			$select = Arr::get($query, 'select', $this->_tb_main_as.'.*');
			if (is_array($select))
			{
				foreach ($select as $sel)
					$this->_query->select($sel);
			}
			else
				$this->_query->select($select);
	
			$this->_query->from(array($this->_tb_main, $this->_tb_main_as));
			$this->build_offset_limit ($start, $limit);
			$this->build_where ($query, 'list');
			$this->build_order_by ($query);
			$this->build_group_by ($query);
			$list  = $this->_query->execute($this->_db_group)->as_array();
		}

		$format_results = Arr::get($query, 'format_results', TRUE);
		$result = array(
			'count' => $count,
			'list'  => $format_results ? $this->build_list ($list) : $list,
		);

		if ($this->_use_cache)
		{
			$this->cached($search_key, $result);
		}

		return $result;
	}

	private function search_key ($query, $start, $limit)
	{
		$this->_cache_key = md5(sprintf("%s,s=%i,l=%i", serialize($query), (int)$start, (int)$limit));
		return $this->_cache_key;
	}

	private function build_offset_limit ($start, $limit)
	{
		if (is_numeric($start))
			$this->_query->offset((int)$start);

		if (is_numeric($limit))
			$this->_query->limit ((int)$limit);
	}

	abstract protected function build_where ($query, $query_type = 'list');

	protected function build_where_greater_less (Array $keys, $query)
	{
		foreach ($keys as $k)
		{
			$gk = "{$k}_greater";
			$lk = "{$k}_less";

			if (isset($query[$gk]))
				$this->_query->where($this->_tb_main_as.'.'.$k, '>=', (int) $query[$gk]);
			if (isset($query[$lk]))
				$this->_query->where($this->_tb_main_as.'.'.$k, '<=', (int) $query[$lk]);
		}
	}

	protected function build_where_keys (Array $keys, $query)
	{
		foreach ($keys as $key => $vs)
		{
			$vkey  = $vs[0];
			$vtype = $vs[1];
			if (isset($query[$key]))
			{
				$v =& $query[$key];
				if (is_array($v) && !empty($v))
					$this->_query->where($vkey, 'in', $query[$key]);
				else 
				{
					if ($vtype == 'like')
						$this->_query->where($vkey, 'like', '%'.$query[$key].'%');
					else
						$this->_query->where($vkey, '=', $query[$key]);
				}
			}
		}
	}

	protected function build_query_status ($status)
	{
		if (is_null($status) or (is_array($status) AND empty($status)))
			return;

		if (is_array($status))
		{
			$vs = array();
			foreach ($status as $s)
			{
				$value = $this->status($s);
				if (NULL !== $value)
					$vs[] = (int) $value;
			}

			if (!empty($vs))
				$this->_query->where($this->_tb_main_as.'.status', 'in', $vs);
		}
		else
		{
			$value = $this->status ($status);
			if (NULL !== $value)
				$this->_query->where($this->_tb_main_as.'.status', '=', $value);
		}
	}

	abstract protected function status ($key);
	abstract protected function build_list ($search_result_list);

	protected function build_order_by ($query)
	{
		if (! isset($query['order_by']) || 
			empty($query['order_by']) )
			return;

		foreach ($query['order_by'] as $key => $order)
		{
			if (strpos($key, '`') === 0)
				$this->_query->order_by(DB::expr($key), $order);
			else
				$this->_query->order_by($this->_tb_main_as.".$key", $order);
		}
	}

	protected function build_group_by ($query)
	{
		$group_by = Arr::get($query, 'group_by');
		if (NULL === $group_by)
			return;

		$this->_query->group_by($this->_tb_main_as.'.'.$group_by);
	}

	/*
	 * 从缓存里获取或者设置搜索结果
	 */
	protected function cache_result ($key, $result = NULL)
	{
		return FALSE;
	}
}
